use std::{
    env,
    fs::*,
    io::{Write},
    path::Path,
};

use clap::Parser;

#[macro_use]
extern crate lazy_static;

pub mod lalr;
pub mod asm;
pub mod lexer;
pub mod parser;

#[derive(Parser)]
#[command(version, author, about, long_about = None)]
struct App {
    /// Source file name.
    src_file: String,

    /// Specify output file name.
    #[arg(short = 'o', default_value = "a.out")]
    output_file: String,

    /// Output file with asm format.
    #[arg(short = 'S', default_value_t = false)]
    as_asm: bool,

    /// Specify grammer source file name.
    #[arg(short = 'g')]
    grammer_file : String,

}

struct AppInfoFromFileSystem {
    asm_gen_settings: asm::settings::AsmCodeGenSettings,
}

fn init_info_from_fs() -> std::io::Result<AppInfoFromFileSystem> {
    let mut cur_dir = env::current_exe()?;
    cur_dir.pop();
    cur_dir = cur_dir.join("config");
    if !cur_dir.exists() {
        create_dir(&cur_dir)?;
    }
    let ag_settings = if !cur_dir.join("asm-code-generator-settings.json").exists() {
        let df = asm::settings::AsmCodeGenSettings::default();
        asm::settings::save_current_settings(
            &cur_dir.join("asm-code-generator-settings.json"),
            &df,
        )?;

        df
    } else {
        asm::settings::read_settings(&cur_dir.join("asm-code-generator-settings.json"))?
    };

    Ok(AppInfoFromFileSystem {
        asm_gen_settings: ag_settings,
    })
}

fn main() -> std::io::Result<()> {
    let info = init_info_from_fs()?;

    let menu_ret = App::parse();

    // LALR
    let grammer_path = Path::new(&menu_ret.grammer_file);
    let grammer_file = File::open(grammer_path).unwrap();

    let mut grammer = lalr::light_grammer_input(grammer_file)?;
    grammer.build_lalr1_kernels();
    println!("{:?}",grammer);

    return Ok(());

    let src_path = Path::new(&menu_ret.src_file);
    let src_file = File::open(src_path).unwrap();

    // Compile Start
    let lex_out = lexer::lex_file(src_file).or_else(|e| {
        println!("Lex failed! {:?}", e);
        Err(std::io::ErrorKind::InvalidInput)
    })?;

    let mut pr = parser::Parser::new(&lex_out);
    let ast = pr.work().or_else(|e| {
        println!("Parse failed! {:?}", e);
        Err(std::io::ErrorKind::InvalidInput)
    })?;

    let mut vc = asm::ast_to_codes(&ast,&info.asm_gen_settings).or_else(|e| {
        println!("Code generation failed! {:?}", e);
        Err(std::io::ErrorKind::InvalidInput)
    })?;

    let fname = src_path
        .file_name()
        .and_then(|s| s.to_str())
        .ok_or_else(|| {
            println!("Cannot get path str.");
            std::io::ErrorKind::InvalidFilename
        })?;

    vc.insert(0, asm::ds_type::Code::File(fname.to_string()));
    let s = asm::codes_to_asmdoc(&vc).or_else(|e| {
        println!("Cannot pack codes to asm doc : {:?}", e);
        Err(std::io::ErrorKind::InvalidInput)
    })?;

    // Output
    let mut out_file = match File::create(&menu_ret.output_file) {
        Err(e) => panic!(
            "Cannot create file {:?} bcuz {:?}.",
            menu_ret.output_file, e
        ),
        Ok(f) => f,
    };

    if menu_ret.as_asm {
        match out_file.write_all(s.as_bytes()) {
            Ok(_) => Ok(()),
            Err(e) => panic!("Cannot write file {:?} bcuz {:?}", menu_ret.output_file, e),
        }
    } else {
        Ok(())
    }
}
